jQuery.url = (function () {
    function utf8_encode(string) {
        string = string.replace(/\r\n/g, "\n");
        var utftext = "";
        for(var n = 0; n < string.length; n++) {
            var c = string.charCodeAt(n);
            if(c < 128) {
                utftext += String.fromCharCode(c);
            } else {
                if((c > 127) && (c < 2048)) {
                    utftext += String.fromCharCode((c >> 6) | 192);
                    utftext += String.fromCharCode((c & 63) | 128);
                } else {
                    utftext += String.fromCharCode((c >> 12) | 224);
                    utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                    utftext += String.fromCharCode((c & 63) | 128);
                }
            }
        }
        return utftext;
    }
    function utf8_decode(utftext) {
        var string = "";
        var i = 0;
        var c = 0;
        var c2 = 0;
        while(i < utftext.length) {
            c = utftext.charCodeAt(i);
            if(c < 128) {
                string += String.fromCharCode(c);
                i++;
            } else {
                if((c > 191) && (c < 224)) {
                    c2 = utftext.charCodeAt(i + 1);
                    string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                    i += 2;
                } else {
                    c2 = utftext.charCodeAt(i + 1);
                    c3 = utftext.charCodeAt(i + 2);
                    string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                    i += 3;
                }
            }
        }
        return string;
    }
    function deparam(params, coerce) {
        var obj = {
        };
        var coerce_types = {
            'true': !0,
            'false': !1,
            'null': null
        };

        $.each(params.replace(/\+/g, ' ').split('&'), function (j, v) {
            var param = v.split('=');
            var key = decode(param[0]);
            var val;
            var cur = obj;
            var i = 0;
            var keys = key.split('][');
            var keys_last = keys.length - 1;

            if(/\[/.test(keys[0]) && /\]$/.test(keys[keys_last])) {
                keys[keys_last] = keys[keys_last].replace(/\]$/, '');
                keys = keys.shift().split('[').concat(keys);
                keys_last = keys.length - 1;
            } else {
                keys_last = 0;
            }
            if(param.length === 2) {
                val = decode(param[1]);
                if(coerce) {
                    val = val && !isNaN(val) ? +val : val === 'undefined' ? undefined : coerce_types[val] !== undefined ? coerce_types[val] : val;
                }
                if(keys_last) {
                    for(; i <= keys_last; i++) {
                        key = keys[i] === '' ? cur.length : keys[i];
                        cur = cur[key] = i < keys_last ? cur[key] || (keys[i + 1] && isNaN(keys[i + 1]) ? {
                        } : []) : val;
                    }
                } else {
                    if($.isArray(obj[key])) {
                        obj[key].push(val);
                    } else {
                        if(obj[key] !== undefined) {
                            obj[key] = [
                                obj[key], 
                                val
                            ];
                        } else {
                            obj[key] = val;
                        }
                    }
                }
            } else {
                if(key) {
                    obj[key] = coerce ? undefined : '';
                }
            }
        });
        return obj;
    }
    function parse(url_str) {
        url_str = url_str || window.location;
        var re = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
        var keys = [
            "source", 
            "protocol", 
            "authority", 
            "userInfo", 
            "user", 
            "password", 
            "host", 
            "port", 
            "relative", 
            "path", 
            "directory", 
            "file", 
            "query", 
            "anchor"
        ];
        var m = re.exec(url_str);
        var uri = {
        };
        var i = keys.length;
        while(i--) {
            uri[keys[i]] = m[i] || "";
        }
        if(uri.query) {
            uri.params = deparam(uri.query, true);
        }
        return uri;
    }
    function build(url_obj) {
        if(url_obj.source) {
            return encodeURI(url_obj.source);
        }
        var resultArr = [];
        if(url_obj.protocol) {
            if(url_obj.protocol == 'file') {
                resultArr.push('file:///');
            } else {
                if(url_obj.protocol == 'mailto') {
                    resultArr.push('mailto:');
                } else {
                    resultArr.push(url_obj.protocol + '://');
                }
            }
        }
        if(url_obj.authority) {
            resultArr.push(url_obj.authority);
        } else {
            if(url_obj.userInfo) {
                resultArr.push(url_obj.userInfo + '@');
            } else {
                if(url_obj.user) {
                    resultArr.push(url_obj.user);
                    if(url_obj.password) {
                        resultArr.push(':' + url_obj.password);
                    }
                    resultArr.push('@');
                }
            }
            if(url_obj.host) {
                resultArr.push(url_obj.host);
                if(url_obj.port) {
                    resultArr.push(':' + url_obj.port);
                }
            }
        }
        if(url_obj.path) {
            resultArr.push(url_obj.path);
        } else {
            if(url_obj.directory) {
                resultArr.push(url_obj.directory);
            }
            if(url_obj.file) {
                resultArr.push(url_obj.file);
            }
        }
        if(url_obj.query) {
            resultArr.push('?' + url_obj.query);
        } else {
            if(url_obj.params) {
                resultArr.push('?' + $.param(url_obj.params));
            }
        }
        if(url_obj.anchor) {
            resultArr.push('#' + url_obj.anchor);
        }
        return resultArr.join('');
    }
    function encode(string) {
        return encodeURIComponent(string);
    }
    function decode(string) {
        string = string || window.location.toString();
        return utf8_decode(unescape(string.replace(/\+/g, ' ')));
    }
    return {
        encode: encode,
        decode: decode,
        parse: parse,
        build: build
    };
})();
